package com.sec.android.app.myfiles.widget.listview;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.log.SamsungAnalyticsLog;
import com.sec.android.app.myfiles.util.animation.AnimateDrawable;
import com.sec.android.app.myfiles.util.animation.MoveAnimation;
import com.sec.android.app.myfiles.util.UiUtils;

public class EditOrderList extends ListView implements AbsListView.OnScrollListener,
        AbsListView.RecyclerListener, View.OnTouchListener {

    private static final String TAG = "EditOrderList";

    private int mLowerBound;
    private int mUpperBound;

    private static int mSrcDragPos = 0;
    private int mDragPos;

    private int mOffsetXInDraggingItem;
    private int mOffsetYInDraggingItem;

    private int mItemWidth;
    private int mItemHeight;

    private int mFirstPos;
    private int mLastPos;

    private int mDragX;
    private int mDragY;

    private boolean mDragging;
    private boolean mProcessingAnimation;

    private AnimateDrawable mDragDrawable;

    private OnMoveCompletedListener mOnMoveCompletedListener;
    private OnScrollListener mOnScrollListener;

    public interface OnMoveCompletedListener {
        void onOrderChanged(int source, int destination);
    }

    private final Runnable mScrollRunnable = new Runnable() {
        @Override
        public void run() {
            if (doScroll() && mDragging) {
                scroll();
            }
        }
    };

    public EditOrderList(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
        setOnTouchListener(this);
        super.setOnScrollListener(this);
        super.setRecyclerListener(this);
    }

    private void init() {
        mProcessingAnimation = false;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            float x = event.getRawX();
            int[] location = new int[2];
            ImageView reorderIcon = (ImageView) v.findViewById(R.id.reorder);

            if (reorderIcon != null) {
                reorderIcon.getLocationOnScreen(location);
                int reorderIconX = location[0];
                if (UiUtils.isInRTLMode(getContext())) {
                    int iconRight = reorderIconX + reorderIcon.getWidth();
                    if (x < iconRight) {
                        mDragging = true;
                    }
                } else {
                    if (x > reorderIconX) {
                        mDragging = true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mProcessingAnimation) {
            return false;
        }

        if (mDragging) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    int pos = pointToPosition(x, y);
                    if (pos <= INVALID_POSITION) {
                        return false;
                    }
                    onPrepareDrag(x, y, pos);
                    SamsungAnalyticsLog.sendLog(SamsungAnalyticsLog.ScreenPath.MANAGE_SHORTCUT, SamsungAnalyticsLog.Event.REORDER, null);
                    break;
                case MotionEvent.ACTION_MOVE:
                    doDrag(x, y);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    stopDrag();
                    break;
                default:
                    break;
            }

            return true;
        }
        return super.onTouchEvent(event);
    }

    private void onPrepareDrag(int i, int j, int p) {
        View localView = getChildAt(p - getFirstVisiblePosition());
        setItemSize(localView.getWidth(), localView.getHeight());
        setDragging(true);
        setDragX(i);
        setDragY(j);
        setFirstPos(getFirstVisiblePosition());
        setLastPos(getLastVisiblePosition());
        setOffsetInDraggingItem(i - localView.getLeft(), j - localView.getTop());

        localView.setDrawingCacheEnabled(true);
        localView.buildDrawingCache(true);
        Bitmap localBitmap = Bitmap.createBitmap(localView.getDrawingCache());
        localView.setDrawingCacheEnabled(false);
        doAnimationForDrag(localBitmap, i, j);
        localView.setVisibility(View.INVISIBLE);

        setSrcDragPos(p);
        setDragPos(p);
        adjustScrollBounds();
    }

    private void doDrag(int i, int j) {
        mDragX = i;
        mDragY = j;
        dragDrawable(i, j);

        int movePos = getItemForPosition(i, j);
        doAnimationForUpdate(movePos);

        if ((mDragY < mUpperBound) || (mDragY > mLowerBound)) {
            scroll();
        }
    }

    private void stopDrag() {
        mProcessingAnimation = true;
        mDragging = false;
        doAnimationForDrop();
    }

    public static void setSrcDragPos(int srcDragPos) {
        mSrcDragPos = srcDragPos;
    }

    public void setDragPos(int dragPos) {
        mDragPos = dragPos;
    }

    public void setDragging(boolean dragging) {
        mDragging = dragging;
    }

    public void setFirstPos(int firstPos) {
        mFirstPos = firstPos;
    }

    public void setLastPos(int lastPos) {
        mLastPos = lastPos;
    }

    public void setDragX(int dragX) {
        mDragX = dragX;
    }

    public void setDragY(int dragY) {
        mDragY = dragY;
    }

    public void setOffsetInDraggingItem(int dx, int dy) {
        mOffsetXInDraggingItem = dx;
        mOffsetYInDraggingItem = dy;
    }

    public void setItemSize(int width, int height) {
        mItemWidth = width;
        mItemHeight = height;
    }

    private void dragDrawable(int x, int y) {
        int i = 0;
        int j = y - mOffsetYInDraggingItem;
        Rect localRect = new Rect(i, j, i + mItemWidth, j + mItemHeight);

        if (mDragDrawable != null && mDragDrawable.getProxy() != null) {
            mDragDrawable.getProxy().setBounds(localRect);
            invalidate();
        }
    }

    private void doAnimationForDrag(Bitmap paramBitmap, int x, int y) {
        int i = x - mOffsetXInDraggingItem;
        int j = y - mOffsetYInDraggingItem;
        final Rect localRect = new Rect(i, j, i + mItemWidth, j + mItemHeight);

        mDragDrawable = new AnimateDrawable(new BitmapDrawable(getResources(), paramBitmap));
        mDragDrawable.getProxy().setBounds(localRect);

        final MoveAnimation dragAnimation = new MoveAnimation(0, 0, 0, 0);
        dragAnimation.setDuration(100L);
        dragAnimation.setFillAfter(true);
        dragAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation arg0) {
            }

            @Override
            public void onAnimationRepeat(Animation arg0) {
            }

            @Override
            public void onAnimationEnd(Animation arg0) {
                dragAnimation.startNow();
                invalidate(localRect);
            }
        });

        mDragDrawable.setAnimation(dragAnimation);
        dragAnimation.startNow();
        invalidate(localRect);
    }

    private void doAnimationForDrop() {
        if (mDragDrawable != null) {
            float x = 0.0f;
            int y = getTop(mDragPos) - mDragDrawable.getProxy().getBounds().top;

            MoveAnimation dropAnimation = new MoveAnimation(0.0f, x, 0.0f, y);
            dropAnimation.setDuration(300);
            dropAnimation.setFillAfter(false);
            dropAnimation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationEnd(Animation paramAnimation) {
                    mDragDrawable = null;
                    if ((mOnMoveCompletedListener != null) && (mDragPos >= 0)
                            && (mDragPos < getCount()) && (mSrcDragPos != mDragPos)) {
                        mOnMoveCompletedListener.onOrderChanged(mSrcDragPos, mDragPos);
                    } else {
                        clearChildViewsAnimation();
                    }
                    mSrcDragPos = -1;
                    endProcessingAnimation();
                }

                @Override
                public void onAnimationRepeat(Animation paramAnimation) {
                }

                @Override
                public void onAnimationStart(Animation paramAnimation) {
                }
            });

            mDragDrawable.setAnimation(dropAnimation);
            dropAnimation.startNow();
            invalidate();
        } else
            endProcessingAnimation();
    }

    private void endProcessingAnimation() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mProcessingAnimation = false;
            }
        }, 100);
    }

    private int getTop(int itemPos) {
        int firstRowTop = getChildAt(0).getTop();
        int row = (itemPos - getFirstVisiblePosition());
        return row * mItemHeight + firstRowTop;
    }

    private int getItemForPosition(int x, int y) {
        int xPos = x;
        int yPos = y - mOffsetYInDraggingItem + mItemHeight / 2;
        int pos;
        int childCount = getChildCount();

        for (pos = 0; pos < childCount; pos++) {
            View itemView = getChildAt(pos);
            Rect localRect = new Rect();
            itemView.getHitRect(localRect);

            if (localRect.contains(xPos, yPos)) {
                break;
            }
        }

        if (pos >= getChildCount()) {
            return mDragPos;
        }

        return pos + getFirstVisiblePosition();
    }

    private void doAnimationForUpdate(int movePos) {
        int i = mDragPos;
        if (mDragPos < movePos) {
            while (mDragPos < movePos) {
                if (movePos < getCount()) {
                    mDragPos++;
                    doAnimationForMove(i, movePos);
                }
            }
        }

        if (mDragPos > movePos) {
            while (mDragPos > movePos) {
                mDragPos--;
                doAnimationForMove(i, movePos);
            }
        }
    }

    private void doAnimationForMove(int dragPos, int movePos) {
        Animation animation;

        if ((mSrcDragPos >= mDragPos) && (dragPos < movePos)) {
            if (!reverseAnimation(mDragPos - 1)) {
                animation = createAnimation(0, 0, getTop(mDragPos) - getTop(mDragPos - 1), 0);
                startAnimationByPosition(mDragPos - 1, animation);
            }
        } else if ((mSrcDragPos < mDragPos) && (dragPos < movePos)) {
            if (!reverseAnimation(mDragPos)) {
                animation = createAnimation(0, 0, 0, getTop(mDragPos - 1) - getTop(mDragPos));
                startAnimationByPosition(mDragPos, animation);
            }
        } else if ((mSrcDragPos <= mDragPos) && (dragPos > movePos)) {
            if (!reverseAnimation(mDragPos + 1)) {
                animation = createAnimation(0, 0, getTop(mDragPos) - getTop(mDragPos + 1), 0);
                startAnimationByPosition(mDragPos + 1, animation);
            }
        } else {
            animation = createAnimation(0, 0, 0, getTop(mDragPos + 1) - getTop(mDragPos));
            startAnimationByPosition(mDragPos, animation);
        }
    }

    private void startAnimationByPosition(int pos, Animation animation) {
        View localView = getChildAt(pos - getFirstVisiblePosition());

        if (localView != null) {
            localView.startAnimation(animation);
        }
    }

    private boolean reverseAnimation(int paramInt) {
        boolean ret = false;
        Animation moveAnimation;

        if (getChildAt(paramInt - getFirstVisiblePosition()) != null) {
            moveAnimation = getChildAt(paramInt - getFirstVisiblePosition()).getAnimation();
            if ((moveAnimation != null) && (moveAnimation.hasStarted()) && (!moveAnimation.hasEnded())) {
                ((MoveAnimation) moveAnimation).reverseAnimation();
                ret = true;
            }
        }

        return ret;
    }

    public void setOnMoveCompletedListener(OnMoveCompletedListener moveCompletedListener) {
        Log.d(TAG, "setOnMoveCompletedListener");
        mOnMoveCompletedListener = moveCompletedListener;
    }

    @Override
    public void setOnScrollListener(AbsListView.OnScrollListener paramOnScrollListener) {
        mOnScrollListener = paramOnScrollListener;
    }

    @Override
    protected void dispatchDraw(Canvas paramCanvas) {
        super.dispatchDraw(paramCanvas);

        if (mDragDrawable != null) {
            mDragDrawable.draw(paramCanvas);

            if ((mDragDrawable != null) && (mDragDrawable.hasStarted()) && (!mDragDrawable.hasEnded())) {
                invalidate();
            }
        }
    }

    private void clearChildViewsAnimation() {
        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View localView = getChildAt(i);
            localView.setVisibility(View.VISIBLE);
            localView.clearAnimation();
        }
    }

    private Animation createAnimation(int fromX, int toX, int fromY, int toY) {
        return new MoveAnimation(fromX, toX, fromY, toY);
    }

    private void scroll() {
        removeCallbacks(mScrollRunnable);
        post(mScrollRunnable);
    }

    private boolean doScroll() {
        int lastVisiblePosition = getLastVisiblePosition();
        int firstVisiblePosition = getFirstVisiblePosition();
        int count = getCount();
        int paddingBottom = getListPaddingBottom();
        int scrollDistance;
        boolean ret = false;

        if (mDragY > mLowerBound) {
            if ((lastVisiblePosition == count - 1)
                    && (getChildAt(lastVisiblePosition - firstVisiblePosition).getBottom() <= getHeight() - paddingBottom)) {
                return false;
            }

            if (mDragY > mLowerBound + (mItemHeight / 4)) {
                scrollDistance = 12;
            } else {
                scrollDistance = 4;
            }

            smoothScrollBy(scrollDistance, 16);

            ret = true;
        } else if (mDragY < mUpperBound) {
            if (getFirstVisiblePosition() == 0 && getChildAt(0).getTop() >= getListPaddingTop()) {
                return false;
            }

            if (mDragY < mUpperBound - (mItemHeight / 4)) {
                scrollDistance = -12;
            } else {
                scrollDistance = -4;
            }

            smoothScrollBy(scrollDistance, 16);

            ret = true;
        }

        for (int i = 0; i < (lastVisiblePosition - firstVisiblePosition); i++) {
            View localView = getChildAt(i);

            if (mSrcDragPos == i + firstVisiblePosition) {
                localView.clearAnimation();
                localView.setVisibility(View.INVISIBLE);
            } else {
                localView.setVisibility(View.VISIBLE);
            }
        }

        return ret;
    }

    private void adjustScrollBounds() {
        mUpperBound = (mItemHeight / 2);
        mLowerBound = (getBottom() - mItemHeight / 2);
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (mDragging) {
            reverseScroll(firstVisibleItem, firstVisibleItem + visibleItemCount - 1);
        }

        if (mOnScrollListener != null) {
            mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }

    private void reverseScroll(int firstVisibleItem, int lastVisibleItem) {
        int i = mFirstPos;
        mFirstPos = firstVisibleItem;
        int j = mLastPos;
        mLastPos = lastVisibleItem;
        int k = getItemForPosition(mDragX, mDragY);

        if ((mSrcDragPos < mDragPos) && (firstVisibleItem < i)) {
            Animation localAnimation2 = createAnimation(0, 0, 0, -mItemHeight);
            localAnimation2.setDuration(0L);
            startAnimationByPosition(firstVisibleItem, localAnimation2);
        } else if (mSrcDragPos > mDragPos && lastVisibleItem > j) {
            Animation localAnimation1 = createAnimation(0, 0, 0, mItemHeight);
            localAnimation1.setDuration(0L);
            startAnimationByPosition(lastVisibleItem, localAnimation1);
        }

        if (mDragPos != k) {
            doAnimationForUpdate(k);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView arg0, int arg1) {
        if (mOnScrollListener != null) {
            mOnScrollListener.onScrollStateChanged(arg0, arg1);
        }
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (adapter == null) {
            return;
        }

        super.setAdapter(adapter);

        adapter.registerDataSetObserver(new DataSetObserver() {
            @Override
            public void onChanged() {
                clearChildViewsAnimation();
                super.onChanged();
            }

            @Override
            public void onInvalidated() {
                clearChildViewsAnimation();
                super.onInvalidated();
            }
        });
    }

    private void clearReverseScrollViewAnimation(View paramView) {
        if (((mSrcDragPos < mDragPos) && (mLowerBound < mDragY)) || ((mSrcDragPos > mDragPos) && (mUpperBound > mDragY))) {
            paramView.clearAnimation();
            paramView.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void onMovedToScrapHeap(View view) {
        clearReverseScrollViewAnimation(view);
    }

}
